package core;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import spatialindex.rtree.RTree;
import spatialindex.spatialindex.IData;
import spatialindex.spatialindex.IEntry;
import spatialindex.spatialindex.INode;
import spatialindex.spatialindex.IQueryStrategy;
import spatialindex.spatialindex.IShape;
import spatialindex.spatialindex.ISpatialIndex;
import spatialindex.spatialindex.IVisitor;
import spatialindex.spatialindex.Point;
import spatialindex.spatialindex.Region;
import spatialindex.storagemanager.DiskStorageManager;
import spatialindex.storagemanager.IBuffer;
import spatialindex.storagemanager.IStorageManager;
import spatialindex.storagemanager.PropertySet;
import spatialindex.storagemanager.RandomEvictionsBuffer;
import util.Constants;
import util.NBGStrParser;
import NET.sourceforge.BplusJ.BplusJ.BplusTree;

/**
 * TIMS query processing entry point<br>
 * get list of string of the format <br>
 * time_stamp1 oid_list1; time_stamp2 oid_list2...
 * 
 * @author Xiaohui
 * 
 */

public class TimsQPV1 {
	static int startPointSeq = 100000;
	List<String> candidate;
	String queryFile, CPRTreeFile, queryType;
	double minTime, maxTime; // query's time interval
	RoadNetworkIndex roadIndexer;
	MovingObjectHolder moholder;
	CPDataIndexer cpidxer;
	// remember all check points
	HashMap<Integer, CheckPoint> allPoints;
	// enable look up from coordinates to node id
	HashMap<String, Integer> loc2id;

	public TimsQPV1(String queryFile, String cprTreeFile,
			String queryType) throws Exception {
		this.queryFile = queryFile;
		this.CPRTreeFile = cprTreeFile;
		this.queryType = queryType;
		candidate = new LinkedList<String>();
		roadIndexer = new RoadNetworkIndex();
		moholder = new MovingObjectHolder();
		cpidxer = new CPDataIndexer();
	}

	/**
	 * Insert into a B+tree with concatenated junction id + time point as key
	 * 
	 * @param timsrtreefile
	 * @param timsblockfile
	 * @throws Exception
	 */
	private void buildBTreeWithCpData() throws Exception {
		System.out.println("Building B+tree with junction data...");
		cpidxer.creatBTree(Constants.timsBTreeFile, Constants.timsBlockFile);
		for (int i : allPoints.keySet()) {
			CheckPoint cp = allPoints.get(i);
			cpidxer.insert(i, cp.getData());
		}
		cpidxer.commitTree();
	}

	

	/**
	 * Intersect with edges and put the junctions of the edge into a set
	 * 
	 * @param allPoints
	 * @throws Exception
	 */
	public Set<Integer> doSpatialFilterWithExtension(QueryParser qparser)
			throws Exception {

		// edge data format
		// eid x1 y1 : x2 y2
		// loc2id format: x,y=>junction id
		if (allPoints == null) {
			System.out
					.println("In doSpatialFilterWithExtension, allPoints is null");
			System.exit(0);
		}
		// long start = System.currentTimeMillis();
		// List<String> edgeData = roadIndexer.RTreeQuery4Data(qparser,
		// Constants.networkRTreeFile, queryType);
		long start = System.currentTimeMillis();
		// doExtension will fill up the candidate list with junctions

		Set<Integer> jidset = roadIndexer.RTreeQuery4Jids(qparser,
				Constants.brNetRTreeFile, queryType);
		long end = System.currentTimeMillis();
		System.out.println("Spatial Filter(ms):" + (end - start));
		// fill up candidate
		// for (int ii : jidset) {
		// CheckPoint tempCp = allPoints.get(ii);
		// if (tempCp != null) {
		// candidate.add(tempCp.getData());
		// }
		// }
		System.out.println("jid set size :" + jidset.size());
		return jidset;
	}

	

	/**
	 * Inefficient doTemporalFilter by scanning the list in junctions
	 * 
	 * @param lowTime
	 * @param highTime
	 * @return
	 */
	public Set<Integer> doTemporalFilterByScanning(double lowTime,
			double highTime) {
		System.out.println("Before temporal filter, candidate size: "
				+ candidate.size());
		Set<Integer> objidSet = new HashSet<Integer>();
		StringTokenizer st;
		StringTokenizer st2;
		long start = System.currentTimeMillis();

		for (String cpdata : candidate) {
			// timestamp is ordered in cp data
			// no need to see more data
			st = new StringTokenizer(cpdata, ";");
			while (st.hasMoreElements()) {
				st2 = new StringTokenizer(st.nextToken(), " ");
				int timeStamp = Integer.parseInt(st2.nextToken());
				if (timeStamp > highTime) {
					break;
				} else if (timeStamp >= lowTime && timeStamp <= highTime) {
					// add satisfied oid into the result set
					while (st2.hasMoreElements()) {
						objidSet.add(Integer.parseInt(st2.nextToken()));
					}
				}
			}
		}
		long end = System.currentTimeMillis();
		System.out.println("Temporal filtering(ms): " + (end - start));
		return objidSet;
	}

	public HashMap<Integer, CheckPoint> getAllPoints() {
		return allPoints;
	}

	public HashMap<String, Integer> getLoc2id() {
		return loc2id;
	}

	private int getPointID(int xloc, int yloc) {
		return loc2id.get(xloc + "," + yloc);
	}

	/**
	 * Fill the loc2id hash map<br>
	 * Enable look up from coordinates to node id
	 * 
	 * @param nodesFile
	 */
	private HashMap<String, Integer> loadNodeFile(String nodesFile) {
		System.out.println("Loading node file...");
		BufferedReader nodesReader;
		HashMap<String, Integer> myset = new HashMap<String, Integer>();
		try {
			// nodes file format:
			nodesReader = new BufferedReader(new FileReader(nodesFile));
			String str;
			while ((str = nodesReader.readLine()) != null) {
				if (str.startsWith("#")) {
					continue;
				}
				StringTokenizer st = new StringTokenizer(str, ",");
				int nodeid = Integer.parseInt(st.nextToken());
				int xloc = Integer.parseInt(st.nextToken());
				int yloc = Integer.parseInt(st.nextToken());
				myset.put(xloc + "," + yloc, nodeid);
			}

			nodesReader.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return myset;
	}

	/**
	 * 1) Read raw data<br>
	 * 2) Create new junctions <br>
	 * 3) Fill allPoints hash map with junction data.<br>
	 * 4) Index with an RTree
	 * 
	 * @param nbgFile
	 * @return a mapping from check point id to a check point
	 */
	public void loadRawData(String nbgFile) {
		if (allPoints == null) {
			allPoints = new HashMap<Integer, CheckPoint>();
		}
		if (loc2id == null) {
			loc2id = loadNodeFile(Constants.nodesFile);
		}
		System.out.println("Loading raw data and index with RTree...");
		int count = 1;
		BufferedReader in;
		NBGStrParser parser = new NBGStrParser();
		try {
			// File access
			in = new BufferedReader(new FileReader(nbgFile));
			String str;
			while ((str = in.readLine()) != null) {
				if (str.startsWith("#")) {
					continue;
				}
				if (count % 10000 == 0) {
					System.out.println("Loaind raw data progress: " + count);
				}
				count++;
				parser.setNBGStr(str);
				if (parser.getState().equals("newpoint")) {
					// new point: remember the start position only
					// Integer oid = parser.getOid(); // object no
					// int timepoint = parser.getTimepoint();
					// start point
					double[] startPointxy = { parser.getCurrPosx(),
							parser.getCurrPosy() };
					allPoints.put(startPointSeq, new StartPoint(
							startPointSeq++, startPointxy, parser.getOid(),
							parser.getTimepoint()));
					// record this approaching junction id for this mo
					moholder.insertMO(new MovingObject(parser.getOid(), parser
							.getApproachingJunctionx(), parser
							.getApproachingJunctiony()));
				} else if (parser.getState().equals("point")) {
					// subsequent run
					// have to care about the change of approaching junction
					int currtime = parser.getTimepoint();
					// approaching junction
					int junctionx = parser.getApproachingJunctionx();
					int junctiony = parser.getApproachingJunctiony();

					// only if junction id has changed, insert into junction
					int prevjx = moholder.getById(parser.getOid()).prevApproachingJunctionx;
					int prevjy = moholder.getById(parser.getOid()).prevApproachingJunctiony;
					// pass the junction already
					if (junctionx != prevjx || junctiony != prevjy) {
						// access database
						int jid = getPointID(prevjx, prevjy);

						// // calculate the time passing the junction
						// double passingTime = calPassTime(currtime, currPosx,
						// currPosy, prevjx, prevjy, parser.getSpeed());
						// update prevjx and prevjy
						moholder.getById(parser.getOid()).prevApproachingJunctionx = junctionx;
						moholder.getById(parser.getOid()).prevApproachingJunctiony = junctiony;
						Junction tempj = (Junction) allPoints.get(jid);
						if (tempj != null) {
							tempj.insertData(currtime - 1, parser.getOid());
						} else {
							double[] junctionxy = { prevjx, prevjy };
							Junction j = new Junction(jid, junctionxy);
							j.insertData(currtime - 1, parser.getOid());
							allPoints.put(jid, j);
						}
					}

				} else if (parser.getState().equals("disappearpoint")) {
					double[] startPointxy = { parser.getCurrPosx(),
							parser.getCurrPosy() };
					int currtime = parser.getTimepoint();

					allPoints.put(startPointSeq, new DisappearPoint(
							startPointSeq++, startPointxy, parser.getOid(),
							currtime));

					// approaching junction
					int junctionx = parser.getApproachingJunctionx();
					int junctiony = parser.getApproachingJunctiony();

					// only if junction id has changed, insert into junction
					int prevjx = moholder.getById(parser.getOid()).prevApproachingJunctionx;
					int prevjy = moholder.getById(parser.getOid()).prevApproachingJunctiony;
					// pass the junction already
					if (junctionx != prevjx || junctiony != prevjy) {
						int jid = getPointID(prevjx, prevjy);

						// update prevjx and prevjy
						moholder.getById(parser.getOid()).prevApproachingJunctionx = junctionx;
						moholder.getById(parser.getOid()).prevApproachingJunctiony = junctiony;
						Junction tempj = (Junction) allPoints.get(jid);
						if (tempj != null) {
							tempj.insertData(currtime - 1, parser.getOid());
						} else {
							double[] junctionxy = { prevjx, prevjy };
							Junction j = new Junction(jid, junctionxy);
							j.insertData(currtime - 1, parser.getOid());
							allPoints.put(jid, j);
						}
					}
				} else {
					System.err.println("Wrong input");
					System.exit(0);
				}
			}
			in.close();

			// load into rtree
			File cprTreeFile = new File(Constants.CPRTreeFile);
			// create cprtree file
			// if (!cprTreeFile.exists()) {
			// RTreeLoadCheckPoint(Constants.CPRTreeFile,
			// Constants.rTreeCapacity, getAllPoints(), "p");
			// }
			// index all check point data with one BTree
			buildBTreeWithCpData();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}


	private void printCandidate() {
		for (String s : candidate) {
			System.out.println(s);
		}
	}
}
